package my.files;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.*;
import static java.nio.file.StandardCopyOption.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;


public class Files extends java.io.File {  //Use anomynous inner classes for threading.  Java work around for lack of function ptr.

    public static final int _MaxPrior = 5;  //Limit to norm since the HDD/Internet is involved.
    public static final int _MinPrior = 3;  //Only want to lower it so much to avoid timing out network ack
    public static long BuffSz = 4096 << 6;  //256k = Intel Atom L2(data)/2
    
    public Path To;                                                
    public static Pattern InvalidPath = Pattern.compile("\\\\" + "\\\\" +  // [Double \]
                                                 "|[\\x00-\\x1F]" + //[nonPrint]
                                                 "|[:<>`]" +        // [NotEscaped]
                                                 "|[" + "\\/" + "\\?"+ "\\*" + "\\|" + "\\\""); //[Escaped ... /?\|"]
    
    public static Pattern ExtPatt = Pattern.compile("\\.[0-z]+$");  //used to get and set the Ext.
    public String Ext = getExt();

    
//============================== Constructors =================================
    public Files (String from, String ... to) {
        super(from);
        if(to.length > 1)      To = Paths.get(to[0]);
    }
    
    public Files (Path from, Path ... to) {
        super(from.toString());
        if(to.length > 1)      To = to[0];
    }
    
    public Files (File from, File ... to) {
        super(from.getPath());
        if(to.length > 1)      To = to[0].toPath();
    }
  
    
//============================== Instant Methods =================================
    //Needs testing
    private String getExt() {
        Matcher m = ExtPatt.matcher(this.toString());
        return (m.find())? m.group(): "";
    }
    
    //Needs testing
    public final Files setExt(String newExt, boolean replaceFile) {
        Matcher m = ExtPatt.matcher(this.toString());
        
        //Ext = getExt();
        Path tmp = Paths.get(m.replaceFirst(newExt));
        FileSystem fs = FileSystems.getDefault();

        if (this.exists() & m.find()) {
            To = Paths.get((m.replaceFirst(newExt)));
            ArrayList<Files> af = new ArrayList();
            af.add(this);
            Thread thd = moveFiles(af, replaceFile, _MaxPrior, af, 1);
            thd.start();
            return new Files(To, this.To);           
        }else{
            return this;
        }
    }
    

    
//============================== Static Methods =================================
    //Needs testing
    public static boolean validatePath(Path path, char replace) throws IOException {  //Allows for either Filename or Absolutepath
        //Check filesystem
        FileSystem fs = FileSystems.newFileSystem(path, null);
        String s = InvalidPath.pattern();
        
        //Edit the pattern string. Run m.find  Filename done separately because file can't contain separator
        Matcher m = InvalidPath.matcher(path.getParent().toString());
        if (fs.getSeparator().matches("/")) {
            s = m.pattern().pattern().replace("\\/", "");  //Allow the '/' for Linux/Unix
            m = Pattern.compile(s).matcher(path.getParent().toString());
        }        
        boolean valid = !m.find() & !path.getFileName().toString().contains(separator);
        
        //Make sure replacement is valid the replace
        Matcher r = Pattern.compile(s).matcher(String.valueOf(replace));
        if(replace != '\0' &  (!r.find(replace)))
            m.replaceAll(String.valueOf(replace));    
        return valid;
    }
    
    /**
     * Tested
     *
     * @param file File to save to
     * @param url Where to retrieve the file
     * @throws MalformedURLException
     * @throws IOException
     */
    public static void saveFile(File file, URL url) throws MalformedURLException, IOException {  //tested

        //Resource List only works in JDK 7+.  Have to use Finally in older JDKs.
        try (InputStream s = url.openStream(); //Resource list doesn't extend to parameters passed
             ReadableByteChannel rbc = Channels.newChannel(s);
             FileOutputStream fos = new FileOutputStream(file);) {

            long k = (long) ((s.available() >> 12) + 1) << 12;
            if ((Files.BuffSz < k) | (k < 4096)) {
                k = Files.BuffSz;
            }
            fos.getChannel().transferFrom(rbc, 0, k);
        }
    } // saveFile

    /**
     * Tested
     *
     * @param c: object type to be deserialized. MUST be and contain only
     * top-level class
     * @param url: where the XML file is retrieved from
     * @return Example: Object m = (MyType)readXml (MyType.class, tvdbUrl);
     * @throws JAXBException
     * @see http://docs.oracle.com/javaee/5/api/javax/xml/bind/Unmarshaller.html
     */
    public static Object readXml(Class<?> c, URL url) throws JAXBException, FileNotFoundException { //Tested
        JAXBContext jc = JAXBContext.newInstance(c);
        Unmarshaller u = jc.createUnmarshaller();
        return u.unmarshal(url);
    } // readXml

    /**
     * Tested
     *
     * @param c: object type to be deserialized. MUST be and contain only
     * top-level class
     * @param f: where the XML file is retrieved from
     * @return Example: MyType m = (MyType) readXml (MyType.class, tvdbUrl);
     * @throws JAXBException
     * @see http://docs.oracle.com/javaee/5/api/javax/xml/bind/Unmarshaller.html
     */
    public static Object readXml(Class<?> c, File f) throws JAXBException { //Tested
        JAXBContext jc = JAXBContext.newInstance(c);
        Unmarshaller u = jc.createUnmarshaller();
        return u.unmarshal(f);
    }


    /*  ============== Sync locked threaded versions HDD involved ===================================
        ============== Consider moving into another package Files.Async when the file becomes larger
        Task: make a threaded java.nio.file.Files.move since the framework's in already static
        Task: look into a general version of thread extend class for HDD/URL functions to be called.
        Make an arraylist of files sorted by sourceDir then destDir to help reduce head moves
        http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#move
    */
    
    /**
     * Needs testing multiple files. And Sync locking.
     * 
     * @param f Files to move or rename
     * @param Replace Replace if destination exists
     * @param tPrior Thread Priority truncated 3 to 5 
     * @param sync object to monitor for thread syncing
     * @param timeOut timeout for thread to wait before executing (ms)
     * @return 
     */
    public static Thread moveFiles(final ArrayList<my.files.Files> f, final boolean Replace,
                                   final int tPrior, final Object sync, final long timeOut) { //Needs Testing
               
        return new Thread("Move Files") {
            private boolean fDaemon = makeThreadSafe(this, tPrior);
            
            public String Err;
            @Override
            public void run() {
                StandardCopyOption[] opt;  //CopyAttr doesn't work on windows
                if(Replace) {
                   opt = new StandardCopyOption[] {ATOMIC_MOVE, REPLACE_EXISTING};
                } else {
                   opt = new StandardCopyOption[] {ATOMIC_MOVE};
                }
                
                //Collections.sort(f);
                synchronized (sync) {
                    try {
                        sync.wait(timeOut);
                        for (Files a : f) {  //suppose to guaranteed order in Java. If not go back to normal for
                            java.nio.file.Files.move(a.toPath(), a.To, opt);
                        }
                        sync.notifyAll();
                    } catch (Exception ex) {  //Thread.setDefaultUncaughtExceptionHandler
                        Err =ex.getMessage();
                    }
                }  //synchronized (sync)
            }  //public void run()
        };  //return new Thread()
    } // public static Thread moveFiles

    
    public static Thread saveFile(final File file, final URL url, final int tPrior, final Object sync) {
        return new Thread() {
            @Override
            public void run() {

                makeThreadSafe(this, tPrior);
                synchronized (sync) {
                    try {
                        saveFile(file, url);
                    } catch (MalformedURLException ex) {
                        Logger.getLogger(Files.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (IOException ex) {
                        Logger.getLogger(Files.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        };
    } //public static Thread saveFile

    //Used to standardize thread properties in package
    private static boolean makeThreadSafe(Thread t, int p) {
        if (!t.isAlive())   t.setDaemon(true);  //avoids orphaning thread.  Needs to be done before the thread starts
        p = (p > _MaxPrior) ? _MaxPrior : p;
        p = (p < _MinPrior) ? _MinPrior : p;
        t.setPriority(p);
        return true; //Allows this function be called in anonymous class while initializing a boolean field.
    }
} //top-level: public class Files

